package com.fubowen.reader.vm

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.cachedIn
import androidx.paging.flatMap
import com.fubowen.reader.adapter.BookChapterPagingSource
import com.fubowen.reader.components.TextPaging
import com.fubowen.reader.db.ThemeDatabase
import com.fubowen.reader.db.databaseOfTheme
import com.fubowen.reader.db.model.BookChapter
import com.fubowen.reader.extension.toTarget
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch

const val NAMESPACE = "READER_"
const val NAMESPACE_CONTENT = NAMESPACE + "CONTENT_"
const val TEXT_SIZE = NAMESPACE_CONTENT + "TEXT_SIZE"
const val LINE_SPACING_MULTIPLIER = NAMESPACE_CONTENT + "LINE_SPACING_MULTIPLIER"
const val LINE_SPACING_EXTRA = NAMESPACE_CONTENT + "LINE_SPACING_EXTRA"
const val HEIGHT = NAMESPACE_CONTENT + "HEIGHT"
const val WIDTH = NAMESPACE_CONTENT + "WIDTH"

const val NAMESPACE_HEADER = NAMESPACE + "HEADER_"
const val HEADER_TEXT_SIZE = NAMESPACE_HEADER + "TEXT_SIZE"
const val HEADER_LINE_SPACING_MULTIPLIER = NAMESPACE_HEADER + "LINE_SPACING_MULTIPLIER"
const val HEADER_LINE_SPACING_EXTRA = NAMESPACE_HEADER + "LINE_SPACING_EXTRA"
const val HEADER_HEIGHT = NAMESPACE_HEADER + "HEIGHT"
const val HEADER_WIDTH = NAMESPACE_HEADER + "WIDTH"


class ReaderViewModel(application: Application) : AndroidViewModel(application) {

    private val mChapterContent = TextPaging(0f, 0f, 60f)
    private val mChapterHeader = TextPaging(0f, 0f, 60f)
    private var mIsInitBookSetting = false
    private val database: ThemeDatabase by databaseOfTheme()
    private var bookDao = database.bookDao()
    private var settingDao = database.settingDao()

    //region 阅读器设置
    fun setHeight(height: Float) {
        mChapterContent.setHeight(height)
        mChapterHeader.setHeight(height)
    }

    fun setBackgroundColor(bgColor: Int) {
        mChapterContent.setBackgroundColor(bgColor)
        mChapterHeader.setBackgroundColor(bgColor)
    }

    fun setColor(color: Int) {
        mChapterContent.setTextColor(color)
        mChapterHeader.setTextColor(color)
    }

    fun setTheme(height: Float) {
        mChapterContent.setHeight(height)
        mChapterHeader.setHeight(height)
    }

    fun setWidth(width: Float) {
        mChapterContent.setWidth(width)
        mChapterHeader.setWidth(width)
    }

    fun setTextSize(value: Float?, isSave: Boolean = false) {
        if (value != null) {
            mChapterContent.textSize = value
            if (isSave) {
                saveSetting(TEXT_SIZE, value)
            }
        }
    }

    fun addTextSize(value: Float?, isSave: Boolean = false) {
        if (value != null) {
            val newValue = mChapterContent.textSize + value
            setTextSize(newValue, isSave)
        }
    }

    fun subTextSize(value: Float?, isSave: Boolean = false) {
        if (value != null) {
            val newValue = mChapterContent.textSize - value
            setTextSize(newValue, isSave)
        }
    }

    fun setLineSpacingMultiplier(value: Float?, isSave: Boolean = false) {
        if (value != null) {
            mChapterContent.lineSpacingMultiplier = value
            if (isSave) {
                saveSetting(LINE_SPACING_MULTIPLIER, value)
            }
        }
    }

    fun setLineSpacingExtra(value: Float?, isSave: Boolean = false) {
        if (value != null) {
            mChapterContent.lineSpacingExtra = value
            if (isSave) {
                saveSetting(LINE_SPACING_EXTRA, value)
            }
        }

    }

    fun setHeaderTextSize(value: Float?, isSave: Boolean = false) {
        if (value != null) {
            mChapterContent.textSize = value
            if (isSave) {
                saveSetting(HEADER_TEXT_SIZE, value)
            }
        }
    }

    fun setHeaderLineSpacingMultiplier(value: Float?, isSave: Boolean = false) {
        if (value != null) {
            mChapterContent.lineSpacingMultiplier = value
            if (isSave) {
                saveSetting(HEADER_LINE_SPACING_MULTIPLIER, value)
            }
        }
    }

    fun setHeaderLineSpacingExtra(value: Float?, isSave: Boolean = false) {
        if (value != null) {
            mChapterContent.lineSpacingExtra = value
            if (isSave) {
                saveSetting(HEADER_LINE_SPACING_EXTRA, value)
            }
        }

    }
    //endregion

    //region 保存阅读进度
    private var mUpdateJob: Job? = null
    private var mLastUpdateProgress = Triple(-1, 0, 0)
    private fun saveReadingProgress(progress: Triple<Int, Int, Int>) =
        viewModelScope.launch {
            val (bookId, id, charIndex) = progress
            bookDao.changeReadingChapter(bookId, id, charIndex)
        }

    fun sendReadingProgress(progress: Triple<Int, Int, Int>) {
        val (oldBookId, oldChapterId, _) = mLastUpdateProgress
        val (newBookId, newChapterId, _) = progress
        if (oldBookId == -1) {
            saveReadingProgress(progress)
        } else {
            if (oldBookId != newBookId) {
                saveReadingProgress(mLastUpdateProgress)
                saveReadingProgress(progress)
            } else if (oldChapterId != newChapterId) {
                saveReadingProgress(mLastUpdateProgress)
                saveReadingProgress(progress)
            } else {
                mUpdateJob?.cancel()
                mUpdateJob = viewModelScope.launch {
                    delay(300)
                    if (isActive) {
                        saveReadingProgress(progress)
                    }
                }
            }
        }
        mLastUpdateProgress = progress
    }
    //endregion

    //region 书籍章节

    private fun initReaderSetting() {
        if (!mIsInitBookSetting) {
            val map = settingDao.map()
            setHeaderTextSize(map[HEADER_TEXT_SIZE].toTarget<Float>())
            setHeaderLineSpacingMultiplier(map[HEADER_LINE_SPACING_MULTIPLIER].toTarget<Float>())
            setHeaderLineSpacingExtra(map[HEADER_LINE_SPACING_EXTRA].toTarget<Float>())

            setTextSize(map[TEXT_SIZE].toTarget<Float>())
            setLineSpacingMultiplier(map[LINE_SPACING_MULTIPLIER].toTarget<Float>())
            setLineSpacingExtra(map[LINE_SPACING_EXTRA].toTarget<Float>())
            mIsInitBookSetting = true
        }
    }

    fun bookChapterFlow(bookId: Int): Flow<PagingData<BookChapter>> {
        val pageSize = 1
        val pageConfig = PagingConfig(
            pageSize = pageSize,
            initialLoadSize = pageSize
        )
        initReaderSetting()
        return Pager(pageConfig) { BookChapterPagingSource(bookId, database) }
            .flow
            .map {
                it.flatMap { chapter ->
                    chapter.textSize = mChapterContent.textSize
                    chapter.lineSpacingMultiplier = mChapterContent.lineSpacingMultiplier
                    chapter.lineSpacingExtra = mChapterContent.lineSpacingExtra
                    chapter.backgroundColor = mChapterContent.bgColor
                    chapter.textColor = mChapterContent.color

                    chapter.headerTextSize = mChapterHeader.textSize
                    chapter.headerLineSpacingMultiplier = mChapterHeader.lineSpacingMultiplier
                    chapter.headerLineSpacingExtra = mChapterHeader.lineSpacingExtra
                    val height = mChapterHeader.getTextHeight(chapter.name)
                    mChapterContent.toPaging(
                        chapter.content,
                        Triple(1, TextPaging.Space.HEIGHT_USED_SPACE, height)
                    ) { page, index ->
                        val item = chapter.copy()
                        item.content = page.joinToString("")
                        item.isPageStart = index == 0
                        item
                    }
                }
            }
            .cachedIn(viewModelScope)
    }
    //endregion

    private fun saveSetting(key: String, value: Any?) {
        viewModelScope.launch {
            settingDao.set(key, value?.toString())
        }
    }

}